/**
  ******************************************************************************
  * @file    YLO_AIP16C23.h
  * @brief   AIP16C23 LCD驱动芯片驱动程序
  * @author  YourName
  * @date    2023-xx-xx
  ******************************************************************************
  */

#include "YLO_AIP16C23.h"

/* 全局变量定义 -------------------------------------------------------------*/
uint8_t Aip16c23DspData[AIP16C23SEGNUM] = {0};  // 显示数据缓冲区

/* 函数定义 -----------------------------------------------------------------*/

/**
  * @brief  微秒级延时函数
  * @note   在32MHz系统时钟下约延时1μs
  * @param  None
  * @retval None
  */
void delay(void)
{
    // 约1μs延时 @32MHz
    for(volatile uint16_t i = 8; i > 0; i--);
}

/**
  * @brief  毫秒级延时函数
  * @note   在32MHz系统时钟下约延时指定毫秒数，同时喂看门狗
  * @param  ms: 延时毫秒数
  * @retval None
  */
void delay_ms(uint16_t ms)
{
    for(uint16_t j = 0; j < ms; j++)
    {
        // 约1ms延时 @32MHz
        for(volatile uint16_t i = 32000; i > 0; i--) 
            WDT->WDT_CON |= WDT_CON_CLRWDT;  // 喂看门狗
    }
}

/**
  * @brief  AIP16C23 GPIO引脚初始化
  * @note   初始化CLK和SDA引脚为推挽输出模式，并设置初始状态
  * @param  None
  * @retval None
  */
void Aip16C23Gpio_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    
    // 配置CLK引脚 - 强推挽输出模式
    GPIO_InitStructure.GPIO_Pin = CLK_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT_PP;
    GPIO_InitStructure.GPIO_DriveLevel = GPIO_DriveLevel_0;  // 最大驱动能力
    GPIO_Init(CLK_PORT, &GPIO_InitStructure);
    
    // 配置SDA引脚 - 初始为强推挽输出模式
    GPIO_InitStructure.GPIO_Pin = SDA_PIN;
    GPIO_Init(SDA_PORT, &GPIO_InitStructure);
    
    // 设置初始状态：CLK和SDA都为高电平
    CLK_HIGH();
    SDA_HIGH();
}

/**
  * @brief  设置SDA引脚为输入模式
  * @note   用于读取从机应答信号时切换SDA为输入模式
  * @param  None
  * @retval None
  */
static void SDA_Input_Mode(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    
    GPIO_InitStructure.GPIO_Pin = SDA_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_HI;  // 高阻输入模式
    GPIO_Init(SDA_PORT, &GPIO_InitStructure);
}

/**
  * @brief  设置SDA引脚为输出模式
  * @note   用于发送数据时切换SDA为输出模式
  * @param  None
  * @retval None
  */
static void SDA_Output_Mode(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    
    GPIO_InitStructure.GPIO_Pin = SDA_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT_PP;  // 强推挽输出模式
    GPIO_InitStructure.GPIO_DriveLevel = GPIO_DriveLevel_0;  // 最大驱动能力
    GPIO_Init(SDA_PORT, &GPIO_InitStructure);
}

/**
  * @brief  I2C起始信号
  * @note   在SCL为高电平时，SDA产生下降沿
  * @param  None
  * @retval None
  */
void AiP16C23_I2c_Start(void)
{
    SDA_Output_Mode();  // 确保SDA为输出模式
    CLK_HIGH();         // SCL拉高
    delay();            // 保持一段时间
    SDA_HIGH();         // SDA拉高
    delay();            
    SDA_LOW();          // SDA拉低，产生起始条件
    delay();
    CLK_LOW();          // SCL拉低，准备数据传输
    delay();
}

/**
  * @brief  I2C停止信号
  * @note   在SCL为高电平时，SDA产生上升沿
  * @param  None
  * @retval None
  */
void AiP16C23_I2c_Stop(void)
{
    SDA_Output_Mode();  // 确保SDA为输出模式
    CLK_LOW();          // SCL拉低
    delay();
    SDA_LOW();          // SDA拉低
    delay();
    CLK_HIGH();         // SCL拉高
    delay();
    SDA_HIGH();         // SDA拉高，产生停止条件
    delay();
}

/**
  * @brief  检测I2C应答信号
  * @note   读取从机返回的ACK信号，低电平表示应答
  * @param  None
  * @retval uint8_t: 1-收到ACK，0-未收到ACK
  */
uint8_t AiP16C23_I2c_Ack(void)
{
    uint8_t ack;
    
    CLK_LOW();              // SCL拉低
    SDA_Output_Mode();      // 先设置为输出
    SDA_HIGH();             // SDA释放
    SDA_Input_Mode();       // 切换为输入模式读取ACK
    delay();
    CLK_HIGH();             // SCL拉高，从机在此期间拉低SDA表示ACK
    delay();
    
    // 读取SDA电平状态，低电平表示ACK
    ack = (SDA_READ() == Bit_RESET) ? 1 : 0;
    
    CLK_LOW();              // SCL拉低
    SDA_Output_Mode();      // 恢复输出模式
    return ack;
}

/**
  * @brief  向AIP16C23写入一个字节数据
  * @note   按照MSB先出的顺序发送8位数据
  * @param  dat: 要写入的数据字节
  * @retval None
  */
void AiP16C23_Write_Byte(uint8_t dat)
{
    uint8_t i;
    SDA_Output_Mode();  // 设置为输出模式
    
    // 循环发送8位数据
    for(i = 0; i < 8; i++)
    {
        CLK_LOW();                  // SCL拉低
        if(dat & 0x80)              // 检查最高位
            SDA_HIGH();             // 高位发送1
        else
            SDA_LOW();              // 高位发送0
        delay();
        CLK_HIGH();                 // SCL拉高，数据被采样
        dat = dat << 1;             // 左移准备下一位
        delay();
    }
    CLK_LOW();          // SCL拉低
    delay();
    AiP16C23_I2c_Ack(); // 检测应答信号
}

/**
  * @brief  从AIP16C23读取一个字节数据
  * @note   按照MSB先出的顺序读取8位数据
  * @param  None
  * @retval uint8_t: 读取到的数据字节
  */
uint8_t AiP16C23_Read_Byte(void)
{
    uint8_t temp = 0, i;
    
    SDA_Input_Mode();  // 设置为输入模式读取数据
    
    // 循环读取8位数据
    for(i = 0; i < 8; i++)
    {
        CLK_HIGH();                 // SCL拉高
        delay();
        temp <<= 1;                 // 左移接收到的数据
        if(SDA_READ() == Bit_SET)   // 读取SDA电平
            temp |= 0x01;           // 设置为1
        CLK_LOW();                  // SCL拉低
        delay();
    }
    
    SDA_Output_Mode();  // 恢复输出模式
    SDA_HIGH();         // 发送非应答信号(NACK)
    CLK_HIGH();         // SCL拉高
    delay();
    CLK_LOW();          // SCL拉低
    delay();
    
    return temp;        // 返回读取的数据
}

/**
  * @brief  向AIP16C23写入命令
  * @note   通过I2C总线发送地址和命令数据
  * @param  addr: 命令地址
  * @param  cmd:  命令数据
  * @retval None
  */
void AiP16C23_Write_Cmd(uint8_t addr, uint8_t cmd)
{
    AiP16C23_I2c_Start();           // 起始信号
    AiP16C23_Write_Byte(0x7c);      // 写从机地址(7位地址+写位)
    AiP16C23_Write_Byte(addr);      // 写通信地址
    AiP16C23_Write_Byte(cmd);       // 写通信数据命令
    AiP16C23_I2c_Stop();            // 停止信号
}

/**
  * @brief  从AIP16C23的EEPROM读取数据
  * @note   读取EEPROM中指定地址的数据到显示缓冲区
  * @param  addr: EEPROM地址
  * @retval None
  */
void AiP16C23_Read_EEP(uint8_t addr)
{
    uint8_t i, RAM_data;
    
    // 设置读取地址
    AiP16C23_I2c_Start();
    AiP16C23_Write_Byte(0x7c);      // 写从机地址
    AiP16C23_Write_Byte(0x80);      // 显示数据输入命令
    AiP16C23_Write_Byte(addr);      // 要读取的地址
    AiP16C23_I2c_Stop();
    
    // 读取数据
    AiP16C23_I2c_Start();
    AiP16C23_Write_Byte(0x7d);      // 读从机地址(7位地址+读位)
    
    // 读取指定长度的数据
    for(i = 0; i < AIP16C23SEGNUM; i++)
    {
        RAM_data = AiP16C23_Read_Byte();  // 读取数据字节
        // 注意：这里读取的数据没有存储到缓冲区，可能需要改进
    }
    AiP16C23_I2c_Stop();
}

/**
  * @brief  清除LCD屏幕显示
  * @note   将所有显示段数据清零
  * @param  None
  * @retval None
  */
void AiP16C23_ClearScreen(void)
{
    uint8_t i;
    
    AiP16C23_I2c_Start();
    AiP16C23_Write_Byte(0x7c);      // 写从机地址
    AiP16C23_Write_Byte(0x80);      // 显示数据输入命令
    AiP16C23_Write_Byte(0x00);      // 显示数据的初始地址
    
    // 写入全0数据清除所有段
    for(i = 0; i < AIP16C23SEGNUM; i++)
    {
        AiP16C23_Write_Byte(0x00);
    }
    
    AiP16C23_I2c_Stop();
}

/**
  * @brief  AIP16C23初始化函数
  * @note   初始化GPIO并配置LCD驱动器的各项参数
  * @param  None
  * @retval None
  */
void AiP16C23_init(void)
{
    Aip16C23Gpio_Init();  // 初始化GPIO引脚
    
    delay_ms(2);          // 短暂延时等待电源稳定
    
    // 配置AIP16C23寄存器
    AiP16C23_Write_Cmd(0x82, 0x00);  // 驱动模式命令 - 1/8duty，1/3bias
    AiP16C23_Write_Cmd(0x84, 0x00);  // 开启内部振荡器，LCD关显示
    AiP16C23_Write_Cmd(0x86, 0x00);  // 频率设定80Hz
    AiP16C23_Write_Cmd(0x88, 0x00);  // 闪烁关闭
    AiP16C23_Write_Cmd(0x8a, 0x20);  // SEG/VLCD引脚设定为SEG引脚，关闭内部电压调整功能
    
    AiP16C23_ClearScreen();  // 清屏
    
    delay_ms(10);             // 延时等待清屏完成
    
    AiP16C23_Write_Cmd(0x84, 0x03);  // 开启内部振荡器，LCD开显示
}

/**
  * @brief  AIP16C23显示驱动函数
  * @note   将显示缓冲区中的数据写入LCD驱动器进行显示
  * @param  None
  * @retval None
  */
void AiP16C23_Driver(void)
{
    uint8_t i;
    
    AiP16C23_I2c_Start();
    AiP16C23_Write_Byte(0x7c);      // 写从机地址
    AiP16C23_Write_Byte(0x80);      // 显示数据输入命令
    AiP16C23_Write_Byte(0x00);      // 显示数据的初始地址
    
    // 将显示缓冲区数据写入LCD
    for(i = 0; i < AIP16C23SEGNUM; i++)
    {
        AiP16C23_Write_Byte(Aip16c23DspData[i]);
    }
    
    AiP16C23_I2c_Stop();
}
